Imports System.Reflection
Namespace Core
    Public NotInheritable Class ColumnDefinition : Inherits Data.DataColumn
        Public mFieldInfo As FieldInfo
        '
        Friend NativeTypeCode As TypeCode
        Private MY_INDEX As Integer ' Has a property to access from outside the class
        Private mAdvancedColumnType As AdvancedColumnType
        Friend mOriginalColumn As ColumnDefinition
        Friend readXXX As MethodInfo
        Friend readBufXXX As MethodInfo
        Friend writeXXX As MethodInfo
#Region "      Constructors "
        Friend Sub New()
            Me.AllowDBNull = False
            Me.ColumnName = Nothing
            Me.Caption = Nothing
            '
            Me.ExtendedProperties.Add("Ascending", True)
            Me.ExtendedProperties.Add("ColumnType", 0I) ' As ColumnType
            Me.ExtendedProperties.Add("ColumnWidth", 0I)
            Me.ExtendedProperties.Add("Decimals", 0I)
            Me.ExtendedProperties.Add("OffSet", 0I)

        End Sub
        ''' <summary>
        ''' Called when created from Sort Order
        ''' </summary>
        ''' <param name="version"></param>
        ''' <param name="fieldinfo"></param>
        ''' <param name="columnAttribute"></param>
        ''' <remarks></remarks>
        Friend Sub New(columnName As String, originalColumn As ColumnDefinition, ascending As Boolean, columnWidth As Integer)
            Me.New()
            '
            Me.ColumnName = columnName : Me.Caption = columnName
            Me.mOriginalColumn = originalColumn
            Me.mFieldInfo = originalColumn.mFieldInfo
            Me.ExtendedProperties("ColumnType") = originalColumn.ExtendedProperties("ColumnType")
            Me.ExtendedProperties("ColumnWidth") = (If(columnWidth > 0, columnWidth, mOriginalColumn.ExtendedProperties("ColumnWidth")))
            Me.ExtendedProperties("Ascending") = ascending
            CheckWidthAndDecimals()
        End Sub
        ''' <summary>
        ''' Called when created from existing DBF file
        ''' </summary>
        ''' <param name="version"></param>
        ''' <param name="fieldinfo"></param>
        ''' <param name="columnAttribute"></param>
        ''' <remarks></remarks>
        Friend Sub New(version As DbfVersion, fieldinfo As FieldInfo, columnAttribute As ColumnAttribute)
            Me.New()
            '
            Me.mFieldInfo = fieldinfo
            If columnAttribute Is Nothing Then
                columnAttribute = New ColumnAttribute()
            End If

            Dim fieldType As Type = mFieldInfo.FieldType
            Me.DataType = fieldType

            If fieldType.IsGenericType AndAlso fieldType.GetGenericTypeDefinition() Is GetType(Nullable(Of )) Then
                Me.AllowDBNull = True
                NativeTypeCode = Type.GetTypeCode(fieldType.GetGenericArguments()(0))
            Else
                Me.NativeTypeCode = Type.GetTypeCode(fieldType)
            End If

            Me.ColumnName = (If(columnAttribute.ColumnName Is Nothing OrElse columnAttribute.ColumnName.Length = 0, fieldinfo.Name, columnAttribute.ColumnName))
            Me.Caption = Me.ColumnName
            '
            Me.ExtendedProperties("ColumnType") = columnAttribute.Type
            mAdvancedColumnType = columnAttribute.AdvancedType
            If columnAttribute.Width > 0 Then Me.ExtendedProperties("ColumnWidth") = columnAttribute.Width
            Me.ExtendedProperties("Decimals") = columnAttribute.Decimals
            Dim defaultWidth As Integer, defaultDecimals As Integer

            If CType(ExtendedProperties("ColumnType"), ColumnType) = ColumnType.UNKNOWN AndAlso mAdvancedColumnType = AdvancedColumnType.UNKNOWN Then
                If NativeTypeCode = TypeCode.[Object] Then
                    GetDefaultColumnType(fieldType, version, CType(Me.ExtendedProperties("ColumnType"), ColumnType), mAdvancedColumnType, defaultWidth, defaultDecimals)
                Else
                    GetDefaultColumnType(NativeTypeCode, Me.AllowDBNull, version, CType(Me.ExtendedProperties("ColumnType"), ColumnType), mAdvancedColumnType, defaultWidth, _
                        defaultDecimals)
                End If
                If CInt(Me.ExtendedProperties("ColumnWidth")) = -1 Then
                    Me.ExtendedProperties("ColumnWidth") = defaultWidth
                End If
                If CInt(Me.ExtendedProperties("Decimals")) = -1 Then
                    Me.ExtendedProperties("Decimals") = defaultDecimals
                End If
            End If
            CheckWidthAndDecimals()
        End Sub
        ''' <summary>
        ''' Called when created from fieldinfo
        ''' </summary>
        ''' <param name="name"></param>
        ''' <param name="type"></param>
        ''' <param name="width"></param>
        ''' <param name="decimals"></param>
        ''' <remarks></remarks>
        Public Sub New(name As String, type As Char, width As Integer, decimals As Integer)
            Me.New()
            '
            Me.ColumnName = name
            Me.Caption = Me.ColumnName
            '
            Select Case type
                Case "C"c
                    Me.ExtendedProperties("ColumnType") = ColumnType.DBASE3_CHARACTER
                    Exit Select
                Case "N"c
                    Me.ExtendedProperties("ColumnType") = ColumnType.DBASE3_NUMBER
                    Exit Select
                Case "L"c
                    Me.ExtendedProperties("ColumnType") = ColumnType.DBASE3_LOGICAL
                    Exit Select
                Case "D"c
                    Me.ExtendedProperties("ColumnType") = ColumnType.DBASE3_DATE
                    Exit Select
                Case "M"c
                    Me.ExtendedProperties("ColumnType") = ColumnType.DBASE3_MEMO
                    Exit Select
                Case "F"c
                    Me.ExtendedProperties("ColumnType") = ColumnType.DBASE4_FLOAT
                    Exit Select
                Case "B"c
                    Me.ExtendedProperties("ColumnType") = ColumnType.DBASE5_BINARY
                    Exit Select
                Case "G"c
                    Me.ExtendedProperties("ColumnType") = ColumnType.DBASE5_GENERAL
                    Exit Select
                Case "P"c
                    Me.ExtendedProperties("ColumnType") = ColumnType.FOXPRO_PICTURE
                    Exit Select
                Case "Y"c
                    Me.ExtendedProperties("ColumnType") = ColumnType.FOXPRO_CURRENCY
                    Exit Select
                Case "T"c
                    Me.ExtendedProperties("ColumnType") = ColumnType.FOXPRO_DATETIME
                    Exit Select
                Case "I"c
                    Me.ExtendedProperties("ColumnType") = ColumnType.FOXPRO_INTEGER
                    Exit Select
                Case "V"c
                    Me.ExtendedProperties("ColumnType") = ColumnType.FOXPRO_VARIFIELD
                    Exit Select
                Case "X"c
                    Me.ExtendedProperties("ColumnType") = ColumnType.FOXPRO_VARCHAR
                    Exit Select
                Case "@"c
                    Me.ExtendedProperties("ColumnType") = ColumnType.FOXPRO_TIMESTAMP
                    Exit Select
                Case "O"c
                    Me.ExtendedProperties("ColumnType") = ColumnType.FOXPRO_DOUBLE
                    Exit Select
                Case "+"c
                    Me.ExtendedProperties("ColumnType") = ColumnType.FOXPRO_AUTOINC
                    Exit Select
                Case Else
                    Throw New Exception(String.Format("Invalid column datatype :'{0}'", type))
            End Select
            Me.ExtendedProperties("ColumnWidth") = width
            Me.ExtendedProperties("Decimals") = decimals
        End Sub
        ''' <summary>
        ''' Called when created for indexes
        ''' </summary>
        ''' <param name="columnName"></param>
        ''' <param name="originalColumn"></param>
        ''' <param name="ascending"></param>
        ''' <param name="width"></param>
        ''' <remarks></remarks>
        Public Shared Sub GetDefaultColumnType(nativeTypeCode As TypeCode, nullable As Boolean, version As DbfVersion, ByRef type As ColumnType, ByRef advancedType As AdvancedColumnType, ByRef defaultWidth As Integer, ByRef defaultDecimals As Integer)
            defaultDecimals = 0
            ' default
            type = ColumnType.UNKNOWN
            advancedType = AdvancedColumnType.UNKNOWN
            If version = DbfVersion.DbfDotNet Then
                Select Case nativeTypeCode
                    Case TypeCode.[Boolean]
                        type = ColumnType.DBASE3_LOGICAL
                        defaultWidth = 1
                        Exit Select
                    Case TypeCode.[Byte]
                        advancedType = AdvancedColumnType.[BYTE]
                        defaultWidth = 1
                        Exit Select
                    Case TypeCode.[SByte]
                        advancedType = AdvancedColumnType.[SBYTE]
                        defaultWidth = 1
                        Exit Select
                    Case TypeCode.Int16
                        advancedType = AdvancedColumnType.INT16
                        defaultWidth = 2
                        Exit Select
                    Case TypeCode.UInt16
                        advancedType = AdvancedColumnType.UINT16
                        defaultWidth = 2
                        Exit Select
                    Case TypeCode.Int32
                        advancedType = AdvancedColumnType.INT32
                        defaultWidth = 4
                        Exit Select
                    Case TypeCode.Int32
                        advancedType = AdvancedColumnType.INT32
                        defaultWidth = 4
                        Exit Select
                    Case TypeCode.[Single]
                        advancedType = AdvancedColumnType.[SINGLE]
                        defaultWidth = 4
                        Exit Select
                    Case TypeCode.Int64
                        advancedType = AdvancedColumnType.INT64
                        defaultWidth = 8
                        Exit Select
                    Case TypeCode.UInt64
                        advancedType = AdvancedColumnType.UINT64
                        defaultWidth = 8
                        Exit Select
                    Case TypeCode.[Double]
                        advancedType = AdvancedColumnType.[DOUBLE]
                        defaultWidth = 8
                        Exit Select
                    Case TypeCode.[Decimal]
                        advancedType = AdvancedColumnType.[DECIMAL]
                        defaultWidth = 13
                        ' 96 bits + 0..28 (4 bits exponent) + sign = 101 bits minimum
                        Exit Select
                    Case TypeCode.DateTime
                        advancedType = AdvancedColumnType.DATETIME
                        defaultWidth = 8
                        ' 64 bits
                        Exit Select
                    Case TypeCode.[String]
                        advancedType = AdvancedColumnType.[STRING]
                        defaultWidth = 20
                        ' unknown
                        Exit Select
                    Case TypeCode.[Char]
                        ' TODO depend on the encoding
                        advancedType = AdvancedColumnType.CHARW
                        defaultWidth = 2
                        Exit Select
                    Case Else
                        Throw New InvalidColumnException(nativeTypeCode.ToString(), Nothing)
                End Select
            Else
                Select Case nativeTypeCode
                    Case TypeCode.[Boolean]
                        type = ColumnType.DBASE3_LOGICAL
                        defaultWidth = 1
                        ' T or F
                        Exit Select
                    Case TypeCode.[Byte]
                        type = ColumnType.DBASE3_NUMBER
                        defaultWidth = 3
                        ' 255
                        Exit Select
                    Case TypeCode.[SByte]
                        type = ColumnType.DBASE3_NUMBER
                        defaultWidth = 4
                        ' -128
                        Exit Select
                    Case TypeCode.Int16
                        type = ColumnType.DBASE3_NUMBER
                        defaultWidth = 6
                        ' -32768
                        Exit Select
                    Case TypeCode.UInt16
                        type = ColumnType.DBASE3_NUMBER
                        defaultWidth = 5
                        ' 65535
                        Exit Select
                    Case TypeCode.Int32
                        type = ColumnType.DBASE3_NUMBER
                        defaultWidth = 11
                        ' -2147483648
                        Exit Select
                    Case TypeCode.Int32
                        type = ColumnType.DBASE3_NUMBER
                        defaultWidth = 10
                        ' 4294967295
                        Exit Select
                    Case TypeCode.[Single]
                        type = ColumnType.DBASE3_NUMBER
                        defaultWidth = 9 + 1
                        ' 7..9 decimals digits of decimals + the DOT
                        defaultDecimals = 2
                        Exit Select
                    Case TypeCode.Int64
                        type = ColumnType.DBASE3_NUMBER
                        defaultWidth = 20
                        ' Int64.MinValue	"-9223372036854775808"	
                        Exit Select
                    Case TypeCode.UInt64
                        type = ColumnType.DBASE3_NUMBER
                        defaultWidth = 19
                        ' UInt64.MaxValue	"18446744073709551615"	
                        Exit Select
                    Case TypeCode.[Double]
                        type = ColumnType.DBASE3_NUMBER
                        defaultWidth = 17 + 1
                        ' 15..17 decimal digits of decimals + the DOT
                        defaultDecimals = 2
                        Exit Select
                    Case TypeCode.[Decimal]
                        type = ColumnType.DBASE3_NUMBER
                        defaultWidth = 30 + 1
                        ' Decimal.MinValue "-79228162514264337593543950335" + the DOT
                        defaultDecimals = 4
                        Exit Select
                    Case TypeCode.DateTime
                        type = ColumnType.DBASE3_DATE
                        defaultWidth = 8
                        Exit Select
                    Case TypeCode.[String]
                        type = ColumnType.DBASE3_CHARACTER
                        defaultWidth = 20
                        ' unknown really
                        Exit Select
                    Case TypeCode.[Char]
                        ' TODO depend on the encoding
                        type = ColumnType.DBASE3_CHARACTER
                        defaultWidth = 1
                        Exit Select
                    Case Else
                        Throw New InvalidColumnException(nativeTypeCode.ToString(), Nothing)
                End Select
            End If
        End Sub
#End Region
        Friend Property Index As Int32
            Get
                Return MY_INDEX
            End Get
            Set(value As Int32)
                MY_INDEX = value
            End Set
        End Property
        Public Property AdvancedColumnType() As AdvancedColumnType
            Get
                Return mAdvancedColumnType
            End Get
            Set(value As AdvancedColumnType)
                mAdvancedColumnType = value
            End Set
        End Property
        Public Shared Sub GetDefaultColumnType(objectType As Type, version As DbfVersion, ByRef type As ColumnType, ByRef advancedType As AdvancedColumnType, ByRef defaultWidth As Integer, ByRef defaultDecimals As Integer)
            defaultDecimals = 0
            If objectType Is GetType(Byte()) Then
                type = ColumnType.UNKNOWN
                advancedType = AdvancedColumnType.BYTES
                defaultWidth = 0
            Else
                Throw New InvalidColumnException(objectType, Nothing)
            End If
        End Sub
        Private Sub CheckWidthAndDecimals()
            Dim minWidth As Integer = 1
            Dim maxWidth As Integer = 255
            Dim minDecimals As Integer = 0
            Dim maxDecimals As Integer = 0
            Dim fixedwidth As Integer = -1
            Dim defaultWidth As Integer = -1
            Dim defaultDecimals As Integer = 0

            Select Case CType(Me.ExtendedProperties("ColumnType"), ColumnType)
                Case ColumnType.DBASE3_LOGICAL
                    fixedwidth = 1
                    ' T or F
                    Exit Select
                    'case ColumnType.INTEGER:
                    '    maxWidth = 20; // Int64.MinValue	"-9223372036854775808"	
                    '    break;
                Case ColumnType.DBASE3_NUMBER
                    defaultWidth = 30 + 1
                    ' Decimal.MinValue "-79228162514264337593543950335" + the DOT
                    maxDecimals = 255
                    Exit Select
                Case ColumnType.DBASE3_CHARACTER
                    Exit Select
                Case ColumnType.DBASE3_DATE
                    'YYYYMMDD
                    fixedwidth = 8
                    Exit Select
                Case ColumnType.UNKNOWN
                    Select Case mAdvancedColumnType
                        Case AdvancedColumnType.DATE_YMD
                            fixedwidth = 3
                            Exit Select
                        Case AdvancedColumnType.DELAYED
                            maxDecimals = 255
                            Exit Select
                        Case AdvancedColumnType.DELETED_FLAG
                            fixedwidth = 1
                            Exit Select
                        Case AdvancedColumnType.BYTES
                            maxWidth = Integer.MaxValue
                            ' we assume you know what you do on this type
                            Exit Select
                        Case AdvancedColumnType.[BYTE], AdvancedColumnType.[SBYTE]
                            fixedwidth = 1
                            Exit Select
                        Case AdvancedColumnType.INT16, AdvancedColumnType.UINT16, AdvancedColumnType.CHARW
                            fixedwidth = 2
                            Exit Select
                        Case AdvancedColumnType.INT32, AdvancedColumnType.INT32, AdvancedColumnType.[SINGLE]
                            fixedwidth = 4
                            Exit Select
                        Case AdvancedColumnType.INT64, AdvancedColumnType.UINT64, AdvancedColumnType.[DOUBLE]
                            fixedwidth = 8
                            Exit Select
                        Case AdvancedColumnType.[DECIMAL]
                            fixedwidth = 13
                            ' 96 bits + 0..28 (4 bits exponent) + sign = 101 bits minimum
                            Exit Select
                        Case AdvancedColumnType.DATETIME
                            fixedwidth = 8
                            ' 64 bits
                            Exit Select
                        Case Else
                            Throw New InvalidColumnException(Me.ExtendedProperties("ColumnType").ToString(), Nothing)
                    End Select
                    Exit Select
                Case Else
                    Throw New InvalidColumnException(Me.ExtendedProperties("ColumnType").ToString(), Nothing)
            End Select

            If fixedwidth >= 0 Then
                minWidth = fixedwidth
                maxWidth = fixedwidth
                defaultWidth = fixedwidth
            End If
            If CInt(Me.ExtendedProperties("ColumnWidth")) = 0 Then
                Me.ExtendedProperties("ColumnWidth") = defaultWidth
            End If
            If CInt(Me.ExtendedProperties("Decimals")) = -1 Then
                Me.ExtendedProperties("Decimals") = defaultDecimals
            End If

            If CInt(Me.ExtendedProperties("ColumnWidth")) < minWidth OrElse CInt(Me.ExtendedProperties("ColumnWidth")) > maxWidth Then
                Throw New InvalidColumnException(Me.ColumnName, (If(minWidth = maxWidth, String.Format("Width must be {0}.", minWidth), String.Format("Width must be between {0} and {1}.", minWidth, maxWidth))))
            End If
            If CInt(Me.ExtendedProperties("Decimals")) < minDecimals OrElse CInt(Me.ExtendedProperties("Decimals")) > maxDecimals Then
                Throw New InvalidColumnException(Me.ColumnName, (If(minWidth = maxWidth, String.Format("Decimals must be {0}.", minDecimals), String.Format("Decimals must be between {0} and {1}.", minDecimals, maxDecimals))))
            End If
        End Sub
        Friend ReadOnly Property DbfColumnType() As Char
            Get
                Select Case CType(Me.ExtendedProperties("ColumnType"), ColumnType)
                    Case ColumnType.DBASE3_CHARACTER
                        Return "C"c
                    Case ColumnType.DBASE3_NUMBER
                        Return "N"c
                    Case ColumnType.DBASE3_LOGICAL
                        Return "L"c
                    Case ColumnType.DBASE3_DATE
                        Return "D"c
                    Case ColumnType.DBASE3_MEMO
                        Return "M"c
                    Case ColumnType.DBASE4_FLOAT
                        Return "F"c
                    Case ColumnType.DBASE5_BINARY
                        Return "B"c
                    Case ColumnType.DBASE5_GENERAL
                        Return "G"c
                    Case ColumnType.FOXPRO_PICTURE
                        Return "P"c
                    Case ColumnType.FOXPRO_CURRENCY
                        Return "Y"c
                    Case ColumnType.FOXPRO_DATETIME
                        Return "T"c
                    Case ColumnType.FOXPRO_INTEGER
                        Return "I"c
                    Case ColumnType.FOXPRO_VARIFIELD
                        Return "V"c
                    Case ColumnType.FOXPRO_VARCHAR
                        Return "X"c
                    Case ColumnType.FOXPRO_TIMESTAMP
                        Return "@"c
                    Case ColumnType.FOXPRO_DOUBLE
                        Return "O"c
                    Case ColumnType.FOXPRO_AUTOINC
                        Return "+"c
                    Case Else
                        Throw New Exception("Invalid column type")
                End Select
            End Get
        End Property
        Friend Function IsMemo() As Boolean
            Return (CType(Me.ExtendedProperties("ColumnType"), ColumnType) = ColumnType.DBASE3_MEMO OrElse CType(Me.ExtendedProperties("ColumnType"), ColumnType) = ColumnType.DBASE5_BINARY OrElse CType(Me.ExtendedProperties("ColumnType"), ColumnType) = ColumnType.DBASE5_GENERAL OrElse CType(Me.ExtendedProperties("ColumnType"), ColumnType) = ColumnType.FOXPRO_VARCHAR OrElse CType(Me.ExtendedProperties("ColumnType"), ColumnType) = ColumnType.FOXPRO_VARIFIELD)
        End Function
        Friend Function NaturalFieldType() As Type
            Select Case CType(Me.ExtendedProperties("ColumnType"), ColumnType)
                Case ColumnType.DBASE3_NUMBER
                    If CInt(Me.ExtendedProperties("Decimals")) = 0 Then
                        Return GetType(Int32)
                    Else
                        Return GetType(Double)
                    End If
                Case ColumnType.DBASE3_DATE
                    Return GetType(DateTime)


                Case ColumnType.DBASE3_CHARACTER
                    Return GetType(String)

                Case ColumnType.DBASE3_LOGICAL
                    Return GetType(Boolean)

                Case ColumnType.DBASE3_MEMO
                    Return GetType(String)

                Case ColumnType.DBASE4_FLOAT
                    Return GetType(Double)
                Case Else
                    Throw New Exception("Unexpected DBF Type:" & Me.ExtendedProperties("ColumnType").ToString())
            End Select
        End Function
    End Class

End Namespace

